﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.DirectoryServices;
    using System.Diagnostics;
    using System.Threading;
    #endregion

    /// <summary>
    /// Objects that are User accounts
    /// </summary>
    [Serializable]
    public class UserAccount : UserGroup, IUserAccount {

        #region Members
        #region Fields
        public static readonly new string[] AttributesToGet;

        #region ObjectCategory/ObjectClass identifiers
        public static readonly string ObjectCategoryShortName = "person";
        public static readonly string ObjectClassShortName = "user";
        #endregion 
        #endregion

        public IExchangeMailboxAttributes ExchangeMailboxAttributes { get; private set; }

        public byte[] JpegPhoto { get; private set; }

        public ILocationAttributes LocationAttributes { get; private set; }

        /// <summary>
        /// This attribute indicates the time when the password of the object will expire. 
        /// Let TO be the object on which the attribute msDS-UserPasswordExpiryTimeComputed is read.
        /// If TO is not in a domain NC, then TO!msDS-UserPasswordExpiryTimeComputed = null. 
        /// Otherwise let D be the root of the domain NC containing TO.
        /// The DC applies the following rules, in the order specified below, to determine the value of TO!msDS-UserPasswordExpiryTimeComputed:
        ///   If any of the ADS_UF_SMARTCARD_REQUIRED, ADS_UF_DONT_EXPIRE_PASSWD, ADS_UF_WORKSTATION_TRUST_ACCOUNT, ADS_UF_SERVER_TRUST_ACCOUNT, ADS_UF_INTERDOMAIN_TRUST_ACCOUNT bits is set in TO!userAccountControl, then TO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF.
        ///   Else, if TO!pwdLastSet = null, or TO!pwdLastSet = 0, then TO!msDS-UserPasswordExpiryTimeComputed = 0.
        ///   Else, if Effective-MaximumPasswordAge = 0x8000000000000000, then TO!msDS-UserPasswordExpiryTimeComputed = 0x7FFFFFFFFFFFFFFF(where Effective - MaximumPasswordAge is defined in [MS - SAMR] section 3.1.1.5).
        ///   Else, TO!msDS-UserPasswordExpiryTimeComputed = TO!pwdLastSet + Effective-MaximumPasswordAge(where Effective-MaximumPasswordAge is defined in [MS-SAMR] section 3.1.1.5).
        /// </summary>
        public DateTime? MSDSUserPasswordExpiryTimeComputedUTC { get; private set; }
        public DateTime? MSDSUserPasswordExpiryTimeComputed { get; private set; }

        #region Windows 2008 new logon attributes
        /// <summary>
        /// The number of failed logon attempts at a Windows Server 2008/Vista or higher computer since the last 
        /// interactive logon feature was enabled
        /// </summary>
        public int MSDSFailedInteractiveLogonCount { get; private set; }

        /// <summary>
        /// The total number of failed interactive logons up until the last successful Ctrl-Alt-Del logon
        /// </summary>
        public int MSDSFailedInteractiveLogonCountAtLastSuccessfulLogon { get; private set; }

        /// <summary>
        /// A time stamp of the last failed logon attempt
        /// </summary>
        public DateTime? MSDSLastFailedInteractiveLogonTime { get; private set; }
        public DateTime? MSDSLastFailedInteractiveLogonTimeUTC { get; private set; }

        /// <summary>
        /// A time stamp of the last successful logon attempt at a Windows Server 2008/Vista or higher computer
        /// </summary>
        public DateTime? MSDSLastSuccessfulInteractiveLogonTime { get; private set; }
        public DateTime? MSDSLastSuccessfulInteractiveLogonTimeUTC { get; private set; }
        #endregion

        /// <summary>
        /// The Password Settings Object that is in effect for the account.
        /// </summary>
        /// <remarks>
        /// Constructed attribute
        /// A PSO may only apply to user accounts or global security groups.  A PSO cannot be applied to an OU or a computer.
        /// A PSO can be linked to more than one group or user, an individual group or user can have more than one PSO linked to it, 
        /// and a user can belong to multiple groups. 
        /// Only one PSO determines the password and lockout settings for a user, called the resultant PSO. 
        /// Each PSO has an precedence number, where 1 indicates the highest precedence. 
        /// If multiple PSOs apply to a user, the PSO with the highest precedence wins. The rules that determine precedence are as follows:
        /// - If multiple PSOs apply to groups to which the user belongs, the PSO with the highest precedence wins. 
        /// - If one or more PSOs are linked directly to the user, PSOs linked to groups are ignored, regardless of their precedence. 
        /// - The user-linked PSO with highest precedence wins. 
        /// - If one or more PSOs have the same precedence value, the PSO with the lowest globally unique identifier (GUID) wins. 
        /// </remarks>
        public string MSDSResultantPSO { get; private set; }

        public IOrganizationAttributes OrganizationAttributes { get; private set; }

        public IRemoteDesktopAttributes RemoteDesktopAttributes { get; private set; }

        public ITelephoneAttributes TelephoneAttributes { get; private set; }

        /// <summary>
        /// The thumbnailPhoto attribute itself supports photos of up to 100K, but the Import-RecipientDataProperty cmdlet 
        /// in Exchange 2010 allows you to import only 10K or smaller files.
        /// </summary>
        public byte[] ThumbnailPhoto { get; private set; }

        public string UserPrincipalName { get; private set; }

        public IReadOnlyList<string> UserWorkstations { get; private set; }

        #region Name properties
        public string GivenName { get; private set; }
        public string Surname { get; private set; }
        #endregion

        #region Profile properties
        public string HomeDrive { get; private set; }
        public string HomeDirectory { get; private set; }
        public string ProfilePath { get; private set; }
        public string ScriptPath { get; private set; }
        #endregion

        #region Remote Desktop properties
        public string MSTSHomeDrive { get; private set; }
        public DateTime? MSTSExpireDateUTC { get; private set; }
        public string MSTSProfilePath { get; private set; }
        public string MSTSWorkDirectory { get; private set; }
        #endregion

        #region IUserComputer properties

        #region Logon and Password DateTimes
        public DateTime? AccountExpires { get; private set; }
        public DateTime? AccountExpiresUTC { get; private set; }

        public DateTime? BadPasswordTime { get; private set; }
        public DateTime? BadPasswordTimeUTC { get; private set; }

        /// <summary>
        /// LastLogonTimeStamp, if it has value.  If not, use WhenChanged, if it has a valid date.  If not, use WhenCreated
        /// </summary>
        public DateTime LastActivity { get; private set; }
        public DateTime LastActivityUTC { get; private set; }

        /// <summary>
        /// Not replicated to all domain controllers.
        /// </summary>
        public DateTime? LastLogon { get; private set; }
        public DateTime? LastLogonUTC { get; private set; }

        /// <summary>
        /// Accurate to within 14 days.  Replicated to all domain controllers and global catalogs in the domain.
        /// http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
        /// </summary>
        public DateTime? LastLogonTimeStamp { get; private set; }
        public DateTime? LastLogonTimeStampUTC { get; private set; }

        /// <summary>
        /// If set, the datetime the account was locked out
        /// </summary>
        public DateTime? LockoutTime { get; private set; }
        public DateTime? LockoutTimeUTC { get; private set; }

        /// <summary>
        /// The datetime the password for the account was set
        /// </summary>
        public DateTime? PwdLastSet { get; private set; }
        public DateTime? PwdLastSetUTC { get; private set; }
        #endregion

        public SupportedEncryptionTypeFlags MSDSSupportedEncryptionTypes { get; private set; }

        /// <summary>
        /// msDS-User-Account-Control-Computed is much like userAccountControl, but the attribute's value can contain additional bits
        /// that are not persisted. The computed bits include the following:
        /// 0x00000010 UF_LOCKOUT
        /// 0x00800000 UF_PASSWORD_EXPIRED
        /// 0x04000000 UF_PARTIAL_SECRETS_ACCOUNT
        /// 0x08000000 UF_USE_AES_KEYS
        /// Constructed attribute
        /// </summary>
        public UserAccountControlFlags MSDSUserAccountControlComputed { get; private set; }

        public UserAccountControlFlags UserAccountControl { get; private set; }

        public PrimaryGroup PrimaryGroup { get; private set; }

        #region Collections
        /// <summary>
        /// The distinguished names of the objects that are managedBy this account. Backlink.
        /// </summary>
        public IReadOnlyList<string> ManagedObjects { get; private set; }

        public IReadOnlyList<string> MSDSAllowedToDelegateTo { get; private set; }

        public IReadOnlyList<string> ServicePrincipalName { get; private set; }
        #endregion

        #endregion

        #region UserAccountControl Helpers
        public bool IsAccountLockedOut {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.AccountLockedOut)
                    || this.MSDSUserAccountControlComputed.HasFlag(UserAccountControlFlags.AccountLockedOut);
            }
        }
        public bool IsAccountSensitiveAndCannotBeDelegated {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.NotDelegated);
            }
        }
        public bool IsAccountDisabled {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.AccountDisabled);
            }
        }
        public bool IsCannotChangePassword {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.CannotChangePassword);
            }
        }
        public bool IsPasswordExpired {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.PasswordExpired)
                    || this.MSDSUserAccountControlComputed.HasFlag(UserAccountControlFlags.PasswordExpired);
            }
        }
        public bool IsPasswordNeverExpires {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.PasswordNeverExpires);
            }
        }
        public bool IsPasswordNotRequired {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.PasswordNotRequired);
            }
        }
        public bool IsSmartCardRequired {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.SmartCardRequired);
            }
        }
        public bool IsTrustedForConstrainedDelegation {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.TrustedToAuthenticateForDelegation);
            }
        }
        public bool IsTrustedForDelegation {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.TrustedForDelegation);
            }
        }
        #endregion

        #endregion

        #region Constructors
        static UserAccount() {
            var attributesToGet = new List<string>();

            attributesToGet.AddRange(new string[]
            {
                "accountExpires",
                "badPasswordTime",
                "givenName",
                "homeDrive",
                "homeDirectory",
                //"jpegPhoto",
                "lastlogon",
                "lastlogontimestamp",
                "lockoutTime",
                "msDS-AllowedToDelegateTo",
                "msDS-FailedInteractiveLogonCount",
                "msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon",
                "msDS-LastFailedInteractiveLogonTime",
                "msDS-LastSuccessfulInteractiveLogonTime",
                "msDS-ResultantPSO",
                "msDS-SupportedEncryptionTypes",
                "msDS-User-Account-Control-Computed",
                "msDS-UserPasswordExpiryTimeComputed",
                "msTSExpireDate",
                "primaryGroupID",
                "profilePath",
                "pwdLastSet",
                "scriptPath",
                "servicePrincipalName",
                "sn",
                //"thumbnailPhoto",
                "userAccountControl",
                "userPrincipalName",
                "userWorkstations"
            });

            attributesToGet.AddRange(Contoso.ActiveDirectory.RemoteDesktopAttributes.AttributesToGet);
            attributesToGet.AddRange(Contoso.ActiveDirectory.OrganizationAttributes.AttributesToGet);
            attributesToGet.AddRange(Contoso.ActiveDirectory.LocationAttributes.AttributesToGet);
            attributesToGet.AddRange(Contoso.ActiveDirectory.TelephoneAttributes.AttributesToGet);
            attributesToGet.AddRange(Contoso.ActiveDirectory.ExchangeMailboxAttributes.AttributesToGet);
            attributesToGet.AddRange(UserGroup.AttributesToGet);

            AttributesToGet = attributesToGet.Distinct().ToArray();
            attributesToGet = null;
        }
        public UserAccount() {
            this.Initialize();
        }

        public UserAccount(DirectoryEntry directoryEntry, bool destroyInputObject = true)
            : base(directoryEntry, destroyInputObject) {
            if (this.GetType().Name == typeof(UserAccount).Name) {
                this.Attributes = null;
            }
        }

        public UserAccount(SearchResult result, bool destroyInputObject = true)
            : base(result, destroyInputObject) {
            if (this.GetType().Name == typeof(UserAccount).Name) {
                this.Attributes = null;
            }
        }

        public UserAccount(List<string> ldifOutput)
            : base(ldifOutput) {
            if (this.GetType().Name == typeof(UserAccount).Name) {
                this.Attributes = null;
            }
        }
        #endregion

        #region Methods
        protected override void Initialize() {
            base.Initialize();

            this.GivenName = string.Empty;
            this.Surname = string.Empty;

            // profile
            this.HomeDrive = string.Empty;
            this.HomeDirectory = string.Empty;
            this.ProfilePath = string.Empty;
            this.ScriptPath = string.Empty;

            this.ManagedObjects = new List<string>();
            this.MSDSResultantPSO = string.Empty;

            this.UserWorkstations = new List<string>();
            this.UserPrincipalName = string.Empty;

            this.LocationAttributes = new LocationAttributes();
            this.OrganizationAttributes = new OrganizationAttributes();
            this.ExchangeMailboxAttributes = new ExchangeMailboxAttributes();
            this.RemoteDesktopAttributes = new RemoteDesktopAttributes(this);
            this.TelephoneAttributes = new TelephoneAttributes();
        }

        protected override void ProcessResults() {

            try {
                base.ProcessResults();

                if (this.Attributes.ContainsKey("givenName")) {
                    this.GivenName = this.Attributes["givenName"][0].ToString();
                }
                if (this.Attributes.ContainsKey("sn")) {
                    this.Surname = this.Attributes["sn"][0].ToString();
                }

                #region Profile
                if (this.Attributes.ContainsKey("homeDrive")) {
                    this.HomeDrive = this.Attributes["homeDrive"][0].ToString();
                }
                if (this.Attributes.ContainsKey("homeDirectory")) {
                    this.HomeDirectory = this.Attributes["homeDirectory"][0].ToString();
                }
                if (this.Attributes.ContainsKey("profilePath")) {
                    this.ProfilePath = this.Attributes["profilePath"][0].ToString();
                }
                if (this.Attributes.ContainsKey("scriptPath")) {
                    this.ScriptPath = this.Attributes["scriptPath"][0].ToString();
                }
                #endregion

                if (this.Attributes.ContainsKey("userWorkstations")) {
                    this.UserWorkstations = new List<string>(this.Attributes["userWorkstations"][0].ToString()
                        .Replace(" ", string.Empty)
                        .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
                }
                if (this.Attributes.ContainsKey("userPrincipalName")) {
                    this.UserPrincipalName = this.Attributes["userPrincipalName"][0].ToString();
                }

                #region Windows Server 2008 new logon attributes
                if (this.Attributes.ContainsKey("msDS-FailedInteractiveLogonCount")) {
                    this.MSDSFailedInteractiveLogonCount = Convert.ToInt32(this.Attributes["msDS-FailedInteractiveLogonCount"][0]);
                }
                if (this.Attributes.ContainsKey("msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon")) {
                    this.MSDSFailedInteractiveLogonCountAtLastSuccessfulLogon = Convert.ToInt32(this.Attributes["msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon"][0]);
                }
                if (this.Attributes.ContainsKey("msDS-LastFailedInteractiveLogonTime")) {
                    this.MSDSLastFailedInteractiveLogonTimeUTC = DateTime.Parse(this.Attributes["msDS-LastFailedInteractiveLogonTime"][0].ToString());
                    if (this.MSDSLastFailedInteractiveLogonTimeUTC != DateTime.MinValue) {
                        this.MSDSLastFailedInteractiveLogonTime = this.MSDSLastFailedInteractiveLogonTimeUTC.Value.ToLocalTime();
                    }
                }
                if (this.Attributes.ContainsKey("msDS-LastSuccessfulInteractiveLogonTime")) {
                    this.MSDSLastSuccessfulInteractiveLogonTimeUTC = DateTime.Parse(this.Attributes["msDS-LastSuccessfulInteractiveLogonTime"][0].ToString());
                    if (this.MSDSLastSuccessfulInteractiveLogonTimeUTC != DateTime.MinValue) {
                        this.MSDSLastSuccessfulInteractiveLogonTime = this.MSDSLastSuccessfulInteractiveLogonTimeUTC.Value.ToLocalTime();
                    }
                }
                #endregion

                #region Logon, password, datetimes
                if (this.Attributes.ContainsKey("accountExpires") && (this.Attributes["accountExpires"][0] != null)) {
                    long accountExpiresTemp = 0;
                    if (long.TryParse(this.Attributes["accountExpires"][0].ToString(), out accountExpiresTemp)) {
                        if ((accountExpiresTemp != 0) && (accountExpiresTemp != long.MinValue) && (accountExpiresTemp != long.MaxValue)) {
                            this.AccountExpiresUTC = DateTime.FromFileTimeUtc(accountExpiresTemp);
                            this.AccountExpires = this.AccountExpiresUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("badPasswordTime") && (this.Attributes["badPasswordTime"][0] != null)) {
                    long badPasswordTimeTemp = 0;
                    if (long.TryParse(this.Attributes["badPasswordTime"][0].ToString(), out badPasswordTimeTemp)) {
                        if ((badPasswordTimeTemp != 0) && (badPasswordTimeTemp != long.MinValue) && (badPasswordTimeTemp != long.MaxValue)) {
                            this.BadPasswordTimeUTC = DateTime.FromFileTimeUtc(badPasswordTimeTemp);
                            this.BadPasswordTime = this.BadPasswordTimeUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("lastlogon") && (this.Attributes["lastlogon"][0] != null)) {
                    long lastLogonTemp = 0;
                    if (long.TryParse(this.Attributes["lastlogon"][0].ToString(), out lastLogonTemp)) {
                        if ((lastLogonTemp != 0) && (lastLogonTemp != long.MinValue) && (lastLogonTemp != long.MaxValue)) {
                            this.LastLogonUTC = DateTime.FromFileTimeUtc(lastLogonTemp);
                            this.LastLogon = this.LastLogonUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("lastlogontimestamp") && (this.Attributes["lastlogontimestamp"][0] != null)) {
                    long lastLogonTimeStampTemp = 0;
                    if (long.TryParse(this.Attributes["lastlogontimestamp"][0].ToString(), out lastLogonTimeStampTemp)) {
                        if ((lastLogonTimeStampTemp != 0) && (lastLogonTimeStampTemp != long.MinValue) && (lastLogonTimeStampTemp != long.MaxValue)) {
                            this.LastLogonTimeStampUTC = DateTime.FromFileTimeUtc(lastLogonTimeStampTemp);
                            this.LastLogonTimeStamp = this.LastLogonTimeStampUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("lockoutTime") && (this.Attributes["lockoutTime"][0] != null)) {
                    long lockoutTimeTemp = 0;
                    if (long.TryParse(this.Attributes["lockoutTime"][0].ToString(), out lockoutTimeTemp)) {
                        if ((lockoutTimeTemp != 0) && (lockoutTimeTemp != long.MinValue) && (lockoutTimeTemp != long.MaxValue)) {
                            this.LockoutTimeUTC = DateTime.FromFileTimeUtc(lockoutTimeTemp);
                            this.LockoutTime = this.LockoutTimeUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("pwdLastSet") && (this.Attributes["pwdLastSet"][0] != null)) {
                    long pwdLastSetTemp = 0;
                    if (long.TryParse(this.Attributes["pwdLastSet"][0].ToString(), out pwdLastSetTemp)) {
                        if ((pwdLastSetTemp != 0) && (pwdLastSetTemp != long.MinValue) && (pwdLastSetTemp != long.MaxValue)) {
                            this.PwdLastSetUTC = DateTime.FromFileTimeUtc(pwdLastSetTemp);
                            this.PwdLastSet = this.PwdLastSetUTC.Value.ToLocalTime();
                        }
                    }
                }

                if (this.Attributes.ContainsKey("msDS-SupportedEncryptionTypes")) {
                    var msDSSupportedEncryptionTypes = SupportedEncryptionTypeFlags.Undefined;
                    if (Enum.TryParse<SupportedEncryptionTypeFlags>(
                        this.Attributes["msDS-SupportedEncryptionTypes"][0].ToString(), ignoreCase: true, result: out msDSSupportedEncryptionTypes)) {
                        this.MSDSSupportedEncryptionTypes = msDSSupportedEncryptionTypes;
                    }
                }

                if (this.Attributes.ContainsKey("msDS-UserPasswordExpiryTimeComputed") && (this.Attributes["msDS-UserPasswordExpiryTimeComputed"][0] != null)) {
                    long msDSUserPasswordExpiryTimeComputedTemp = 0;
                    if (long.TryParse(this.Attributes["msDS-UserPasswordExpiryTimeComputed"][0].ToString(), out msDSUserPasswordExpiryTimeComputedTemp)) {
                        if ((msDSUserPasswordExpiryTimeComputedTemp != long.MinValue) && (msDSUserPasswordExpiryTimeComputedTemp != long.MaxValue)) {
                            this.MSDSUserPasswordExpiryTimeComputedUTC = DateTime.FromFileTimeUtc(msDSUserPasswordExpiryTimeComputedTemp);
                            this.MSDSUserPasswordExpiryTimeComputed = this.MSDSUserPasswordExpiryTimeComputedUTC.Value.ToLocalTime();
                        }
                    }
                }

                if (this.Attributes.ContainsKey("primaryGroupID")) {
                    var primaryGroup = ActiveDirectory.PrimaryGroup.Undefined;
                    if (Enum.TryParse<PrimaryGroup>(this.Attributes["primaryGroupID"][0].ToString(), out primaryGroup)) {
                        this.PrimaryGroup = primaryGroup;
                    }
                }

                if (this.Attributes.ContainsKey("msDS-User-Account-Control-Computed")) {
                    var userAccountControlComputed = UserAccountControlFlags.NotSet;
                    if (Enum.TryParse<UserAccountControlFlags>(this.Attributes["msDS-User-Account-Control-Computed"][0].ToString(), out userAccountControlComputed)) {
                        this.MSDSUserAccountControlComputed = userAccountControlComputed;
                    }
                }

                if (this.Attributes.ContainsKey("msTSExpireDate")) {
                    DateTime msTSExpireDateUTC;
                    if (DateTime.TryParse(this.Attributes["msTSExpireDate"][0].ToString(), out msTSExpireDateUTC)) {
                        this.MSTSExpireDateUTC = msTSExpireDateUTC;
                    }
                }

                if (this.Attributes.ContainsKey("userAccountControl")) {
                    var userAccountControl = UserAccountControlFlags.NotSet;
                    if (Enum.TryParse<UserAccountControlFlags>(this.Attributes["userAccountControl"][0].ToString(), out userAccountControl)) {
                        this.UserAccountControl = userAccountControl;
                    }
                }

                this.ServicePrincipalName = ADDataAccess.GetMultiValuedAttribute<string>("servicePrincipalName", this.Attributes);
                this.MSDSAllowedToDelegateTo = ADDataAccess.GetMultiValuedAttribute<string>("msDS-AllowedToDelegateTo", this.Attributes);

                if (this.LastLogonTimeStampUTC.HasValue) {
                    this.LastActivityUTC = this.LastLogonTimeStampUTC.Value;
                }
                else if (this.WhenChangedUTC != DateTime.MinValue) {
                    this.LastActivityUTC = this.WhenChangedUTC;
                }
                else if (this.WhenCreatedUTC != DateTime.MinValue) {
                    this.LastActivityUTC = this.WhenCreatedUTC;
                }
                if (this.LastActivityUTC != DateTime.MinValue) {
                    this.LastActivity = this.LastActivityUTC.ToLocalTime();
                }
                #endregion

                if (this.Attributes.ContainsKey("msDS-ResultantPSO")) {
                    this.MSDSResultantPSO = this.Attributes["msDS-ResultantPSO"][0].ToString();
                }

                this.LocationAttributes.ProcessResults(this.Attributes);
                this.OrganizationAttributes.ProcessResults(this.Attributes);
                this.ExchangeMailboxAttributes.ProcessResults(this.Attributes);
                this.TelephoneAttributes.ProcessResults(this.Attributes);
                this.RemoteDesktopAttributes.ProcessResults(this.Attributes);

                this.ManagedObjects = ADDataAccess.GetMultiValuedAttribute<string>("managedObjects", this.Attributes);

                #region Photos
                if (this.Attributes.ContainsKey("jpegPhoto") && (this.Attributes["jpegPhoto"][0] != null)) {
                    this.JpegPhoto = (byte[])this.Attributes["jpegPhoto"][0];
                }
                if (this.Attributes.ContainsKey("thumbnailPhoto") && (this.Attributes["thumbnailPhoto"][0] != null)) {
                    this.ThumbnailPhoto = (byte[])this.Attributes["thumbnailPhoto"][0];
                }
                #endregion

                this.IsValid = !string.IsNullOrWhiteSpace(this.DN)
                && this.ObjectClass.Any(x => string.Equals(x, ObjectCategoryShortName, StringComparison.OrdinalIgnoreCase));
            }
            catch (Exception e) {
                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Error: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), e.VerboseExceptionString()));
                throw;
            }

            // handle case for blank display name
            if (string.IsNullOrWhiteSpace(this.DisplayName)) {
                if (!string.IsNullOrWhiteSpace(this.Surname)) this.DisplayName += this.Surname;
                if (!string.IsNullOrWhiteSpace(this.GivenName)) this.DisplayName += ", " + this.GivenName;
                if (string.IsNullOrWhiteSpace(this.DisplayName)) this.DisplayName = this.CN;
            }
        }

        [DebuggerStepThroughAttribute]
        public override string ToString() {
            if (!string.IsNullOrWhiteSpace(this.DomainPlusName)) {
                return this.DomainPlusName;
            }
            else {
                return this.DN;
            }
        }
        #endregion
    }
}
